﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

namespace Xenta.Utils
{
    /// <summary>
    /// Represents the image helper.
    /// </summary>
    public static class ImageHelper
    {
        #region Constants

        private const int CAPTCHA_DENSITY = 30;

        #endregion

        #region Methods

        /// <summary>
        /// Generates the captcha image.
        /// </summary>
        /// <param name="text">The text to encode in image.</param>
        /// <param name="imgWidth">The image width.</param>
        /// <param name="imgHeight">The image height.</param>
        /// <returns>The captcha image data.</returns>
        public static byte[] Captcha(string text, int imgWidth, int imgHeight)
        {
            using(var bmp = new Bitmap(imgWidth, imgHeight, PixelFormat.Format24bppRgb))
            {
                var rand = new Random();
                var colors = new[]
                {
                    Color.Red, 
                    Color.Green, 
                    Color.Blue, 
                    Color.Black
                };

                using(var g = Graphics.FromImage(bmp))
                {
                    int charOffset = 0;
                    g.SmoothingMode = SmoothingMode.AntiAlias;

                    g.Clear(Color.White);
                    foreach(char c in text)
                    {
                        using(var f = new Font("Arial", Convert.ToInt32(imgHeight * 0.7), FontStyle.Bold))
                        {
                            using(var fBrush = new SolidBrush(colors[rand.Next(0, colors.Length)]))
                            {
                                var gPath = new GraphicsPath();
                                double charWidth = imgWidth / text.Length;
                                var charRect = new Rectangle(Convert.ToInt32(charOffset * charWidth), 0, Convert.ToInt32(charWidth), imgHeight);

                                gPath.AddString(c.ToString(), f.FontFamily, (int)f.Style, f.Size, charRect, new StringFormat
                                {
                                    Alignment = StringAlignment.Near,
                                    LineAlignment = StringAlignment.Near
                                });
                                g.FillPath(fBrush, gPath);

                                charOffset += 1;
                            }
                        }

                        var imgRect = new Rectangle(new Point(0, 0), bmp.Size);
                        using(var br = new SolidBrush(colors[rand.Next(0, colors.Length)]))
                        {
                            int size = 40;
                            int max = Convert.ToInt32(Math.Max(imgRect.Width, imgRect.Height) / size);

                            for(int i = 0; i <= Convert.ToInt32((imgRect.Width * imgRect.Height) / CAPTCHA_DENSITY); i++)
                                g.FillEllipse(br, rand.Next(imgRect.Width), rand.Next(imgRect.Height), rand.Next(max), rand.Next(max));
                        }

                        int length = 3;
                        float lineWidth = Convert.ToSingle(imgHeight / 31.25);
                        var linePoint = new PointF[length + 1];
                        using(var pen = new Pen(Color.Black, lineWidth))
                        {
                            for(int i = 0; i <= length; i++)
                                linePoint[i] = new PointF(rand.Next(imgRect.Left, imgRect.Width), rand.Next(imgRect.Top, imgRect.Bottom));
                            g.DrawCurve(pen, linePoint, 1.75F);
                        }
                    }
                }
                using(var ms = new MemoryStream())
                {
                    bmp.Save(ms, ImageFormat.Gif);
                    return ms.ToArray();
                }
            }
        }

        /// <summary>
        /// Resizes the image to a target size.
        /// </summary>
        /// <param name="inStream">An image input stream.</param>
        /// <param name="outStream">An image output stream.</param>
        /// <param name="size">The target size.</param>
        /// <remarks>The target size specifies the biggest side of the image.</remarks>
        public static void Resize(Stream inStream, Stream outStream, int size)
        {
            int width = 0;
            int height = 0;

            using(var img = Image.FromStream(inStream))
            {
                if(img.Height > img.Width)
                {
                    height = size;
                    width = (int)(img.Width * (size / (float)img.Height));
                }
                else
                {
                    width = size;
                    height = (int)(img.Height * (size / (float)img.Width));
                }
            }
            Resize(inStream, outStream, width, height);
        }

        /// <summary>
        /// Resizes the image to a target size.
        /// </summary>
        /// <param name="inStream">An image input stream.</param>
        /// <param name="outStream">An image output stream.</param>
        /// <param name="width">The target width.</param>
        /// <param name="height">The target height.</param>
        public static void Resize(Stream inStream, Stream outStream, int width, int height)
        {
            if(inStream == null)
                throw new ArgumentNullException("inStream");
            if(outStream == null)
                throw new ArgumentNullException("outStream");
            using(var img = Image.FromStream(inStream))
            {
                using(var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb))
                {
                    bmp.SetResolution(72, 72);
                    using(var g = Graphics.FromImage(bmp))
                    {
                        g.SmoothingMode = SmoothingMode.AntiAlias;
                        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        g.PixelOffsetMode = PixelOffsetMode.HighQuality;

                        g.Clear(Color.White);
                        g.DrawImage(img, 
                            new Rectangle(0, 0, width, height), 
                            0, 
                            0, 
                            img.Width, 
                            img.Height, 
                            GraphicsUnit.Pixel);
                        bmp.Save(outStream, img.RawFormat);
                    }
                }
            }
        }

        /// <summary>
        /// Adds the watermark to an image.
        /// </summary>
        /// <param name="inStream">An image input stream.</param>
        /// <param name="outStream">An image output stream.</param>
        /// <param name="text">The watermark text.</param>
        /// <param name="position">
        /// The watermark position, allowed values: 
        /// tl - top-left, 
        /// tc - top-center,
        /// tr - top-right, 
        /// bl - bottom-left, 
        /// bc - bottom-center,
        /// br - bottom-right(default),
        /// cc - center.
        /// </param>
        /// <param name="padding">The padding value, 0 by default.</param>
        /// <param name="bgClrArgb">The backgroud color(ARGB), "255, 0, 0, 0" by default.</param>
        /// <param name="fgClrArgb">The foreground color(ARGB), "255, 255, 255, 255" by default.</param>
        /// <param name="fontFamily">The font family name, "Arial" by default.</param>
        /// <param name="fontSize">The font size, 12 by default.</param>
        public static void Watermark(Stream inStream, 
            Stream outStream, 
            string text,
            string position = "br",
            int padding = 0,
            string bgClrArgb = "255, 0, 0, 0",
            string fgClrArgb = "255, 255, 255, 255",
            string fontFamily = "Arial",
            float fontSize = 12)
        {
            if(inStream == null)
                throw new ArgumentNullException("inStream");
            if(outStream == null)
                throw new ArgumentNullException("outStream");
            if(String.IsNullOrWhiteSpace(text))
                throw new ArgumentNullException("text");

            position = StringHelper.EnsureNotNull(position).Trim();
            bgClrArgb = StringHelper.EnsureNotNull(bgClrArgb).Trim();
            fgClrArgb = StringHelper.EnsureNotNull(fgClrArgb).Trim();
            fontFamily = StringHelper.EnsureNotNull(fontFamily).Trim();

            if(position.Length != 2)
                position = "br";
            if(String.IsNullOrEmpty(fontFamily))
                fontFamily = "Arial";

            using(var img = Image.FromStream(inStream))
            {
                using(var bmp = new Bitmap(img.Width, img.Height, PixelFormat.Format24bppRgb))
                {
                    using(var g = Graphics.FromImage(bmp))
                    {
                        g.Clear(Color.White);
                        g.DrawImage(img,
                            new Rectangle(0, 0, bmp.Width, bmp.Height),
                            0,
                            0,
                            bmp.Width,
                            bmp.Height,
                            GraphicsUnit.Pixel);

                        var rectBrush = new SolidBrush(bgClrArgb.ParseAs(Color.Black));
                        var textBrush = new SolidBrush(fgClrArgb.ParseAs(Color.White));
                        using(var f = new Font(fontFamily, fontSize))
                        {
                            var txtSize = g.MeasureString(text, f);
                            float txtPosX = 0;
                            float txtPosY = 0;

                            position = position.ToLowerInvariant();
                            switch(position[0])
                            {
                                case 't':
                                    txtPosY = padding;
                                    break;
                                case 'c':
                                    txtPosY = ((float)bmp.Height / 2) - (txtSize.Height / 2) - ((float)padding / 2);
                                    break;
                                case 'b':
                                default:
                                    txtPosY = bmp.Height - txtSize.Height - padding;
                                    break;
                            }
                            switch(position[1])
                            {
                                case 'l':
                                    txtPosX = padding;
                                    break;
                                case 'c':
                                    txtPosX = ((float)bmp.Width / 2) - (txtSize.Width / 2) - ((float)padding / 2);
                                    break;
                                case 'r':
                                default:
                                    txtPosX = bmp.Width - txtSize.Width - padding;
                                    break;
                            }

                            var rect = new RectangleF
                            {
                                X = txtPosX,
                                Y = txtPosY,
                                Size = txtSize
                            };
                            g.FillRectangle(rectBrush, rect);
                            g.DrawString(text, f, textBrush, rect.X, rect.Y);
                        }
                        bmp.Save(outStream, img.RawFormat);
                    }
                }
            }
        }

        #endregion
    }
}
